//**************************************************
// Description: convert continuous 32-bit data to continuous 128-bit data 
//**************************************************

module rx_bus_convert #(
    parameter IN_MOD_WIDTH      = 2 ,
    parameter IN_DATA_WIDTH     = 32 ,
    parameter IN_BYTE_PER_PHASE = 4 , // equal to "2^(IN_MOD_WIDTH)"
    parameter OUT_MOD_WIDTH     = 4 ,
    parameter OUT_DATA_WIDTH    = 128
        ) (
    input                           clk     ,
    input                           rst_n   ,
    input   [9:0]                   ram_2p_cfg_register,
    // pre-module interface
    output                          bus_convert_rx_rdy ,
    input   [IN_DATA_WIDTH-1:0]     bus_convert_rx_data ,
    input   [IN_MOD_WIDTH-1:0]      bus_convert_rx_mod ,
    input                           bus_convert_rx_sav ,
    input                           bus_convert_rx_val ,
    input                           bus_convert_rx_sop ,
    input                           bus_convert_rx_eop ,
    input   [5:0]                   bus_convert_rx_err ,
    input   [10:0]                  rx_frame_length ,
    // post-module interface
    input                           unpack_rdy ,
    output  [OUT_DATA_WIDTH-1:0]    unpack_data ,
    output  [OUT_MOD_WIDTH-1:0]     unpack_mod ,
    output                          unpack_sav ,
    output                          unpack_val ,
    output                          unpack_sop ,
    output                          unpack_eop
) ;
//**************************************************
// FIFO input FSM
//**************************************************
    //----------------------------------------------
    // signal declare
    //----------------------------------------------
    reg [OUT_DATA_WIDTH-1:0]    frame_input_data_tmp ;      // store the FIFO input data 
    reg [OUT_MOD_WIDTH-1:0]     frame_input_mod_tmp ;       // store the FIFO input mod 
    reg [OUT_DATA_WIDTH-1:0]    frame_input_data_tmp_ff1 ;
    reg [OUT_MOD_WIDTH-1:0]     frame_input_mod_tmp_ff1 ;   // the 2nd FIFO input FSM need a delay
    wire                        frame_input_mod_all_zero ;  // if "mod" is 0, means "mod+4"

    // DFF for "sop" signal
    reg                         frame_input_sop_ff1 ;
    reg                         frame_input_sop_ff2 ;
    reg                         frame_input_sop_ff3 ;
    reg                         frame_input_sop_ff4 ; // for "FIFO input" port

    // hand_shaking between "input_FSM" and "output_FSM"
    reg                         input_FSM_finish ;
    reg                         output_FSM_rdy ;
    
    //----------------------------------------------
    // FSM declare
    //----------------------------------------------
    reg [2:0] frame_in_state_c ;
    reg [2:0] frame_in_state_n ;
    reg [2:0] frame_in_state_c_ff1 ; // be used for "fifo input port"
    localparam IN_IDLE           = 3'd0 ;
    localparam IN_REC_STAGE1     = 3'd1 ; // has received 1st stage
    localparam IN_REC_STAGE2     = 3'd2 ; // has received 2st stage
    localparam IN_REC_STAGE3     = 3'd3 ; // has received 3nd stage
    localparam IN_REC_STAGE4     = 3'd4 ; // has received 4th stage
    localparam IN_REC_END        = 3'd6 ;
    
    // input FSM (1.1)
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            frame_in_state_c <= IN_IDLE ;
        end
        else begin
            frame_in_state_c <= frame_in_state_n ;
        end
    end
    // input FSM (1.2)
    always @(*) begin
        case(frame_in_state_c)
            IN_IDLE: begin
                if(bus_convert_rx_sop == 1'b1) begin
                    frame_in_state_n = IN_REC_STAGE1 ;
                end
                else begin
                    frame_in_state_n = IN_IDLE ;
                end
            end

            IN_REC_STAGE1: begin
                if(bus_convert_rx_val == 1'b1) begin
                    if(bus_convert_rx_eop == 1'b1) begin
                        frame_in_state_n = IN_REC_END ;
                    end
                    else begin
                        frame_in_state_n = IN_REC_STAGE2 ;
                    end
                end
                else
                    frame_in_state_n = IN_REC_STAGE1 ;
            end

            IN_REC_STAGE2: begin
                if(bus_convert_rx_val == 1'b1) begin
                    if(bus_convert_rx_eop == 1'b1) begin
                        frame_in_state_n = IN_REC_END ;
                    end
                    else begin
                        frame_in_state_n = IN_REC_STAGE3 ;
                    end
                end
                else
                    frame_in_state_n = IN_REC_STAGE2 ;
            end

            IN_REC_STAGE3: begin
                if(bus_convert_rx_val == 1'b1) begin
                    if(bus_convert_rx_eop == 1'b1) begin
                        frame_in_state_n = IN_REC_END ;
                    end
                    else begin
                        frame_in_state_n = IN_REC_STAGE4 ;
                    end
                end
                else
                    frame_in_state_n = IN_REC_STAGE3 ;
            end

            IN_REC_STAGE4: begin
                if(bus_convert_rx_val == 1'b1) begin
                    if(bus_convert_rx_eop == 1'b1) begin
                        frame_in_state_n = IN_REC_END ;
                    end
                    else begin
                        frame_in_state_n = IN_REC_STAGE1 ;
                    end
                end
                else
                    frame_in_state_n = IN_REC_STAGE1 ;
            end

            IN_REC_END: begin
                if(output_FSM_rdy==1'b1) begin
                    frame_in_state_n = IN_IDLE ;
                end
                else begin
                    frame_in_state_n = IN_REC_END ;
                end
            end

            default:
                frame_in_state_n = IN_IDLE ;
        endcase
    end

    // input FSM (1.3)
    assign frame_input_mod_all_zero = !(|bus_convert_rx_mod) ;

    // gen "frame_input_data_tmp" and "frame_input_mod_tmp"
    always@(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            frame_input_data_tmp <= {OUT_DATA_WIDTH{1'b0}} ;
            frame_input_mod_tmp  <= {OUT_MOD_WIDTH{1'b0}} ;
        end
        else begin
            if(bus_convert_rx_val == 1'b1) begin
                case( {frame_in_state_c, bus_convert_rx_eop} )
                    ({ IN_IDLE, 1'b0 }): begin
                        frame_input_data_tmp <= { bus_convert_rx_data, {(OUT_DATA_WIDTH-IN_DATA_WIDTH){1'b0}} } ;
                        frame_input_mod_tmp <=  IN_BYTE_PER_PHASE ;
                    end

                    ({ IN_REC_STAGE1, 1'b0 }): begin
                        frame_input_data_tmp <= { frame_input_data_tmp[OUT_DATA_WIDTH-1:OUT_DATA_WIDTH-IN_DATA_WIDTH], bus_convert_rx_data, {(OUT_DATA_WIDTH-2*IN_DATA_WIDTH){1'b0}} } ;
                        frame_input_mod_tmp <=  frame_input_mod_tmp + IN_BYTE_PER_PHASE ;
                    end
                    ({ IN_REC_STAGE1, 1'b1 }): begin
                        frame_input_data_tmp <= { frame_input_data_tmp[OUT_DATA_WIDTH-1:OUT_DATA_WIDTH-IN_DATA_WIDTH], bus_convert_rx_data, {(OUT_DATA_WIDTH-2*IN_DATA_WIDTH){1'b0}} } ;
                        if(frame_input_mod_all_zero)
                            frame_input_mod_tmp <=  frame_input_mod_tmp + IN_BYTE_PER_PHASE ;
                        else
                            frame_input_mod_tmp <=  frame_input_mod_tmp + bus_convert_rx_mod ;
                    end

                    ({ IN_REC_STAGE2, 1'b0 }): begin
                        frame_input_data_tmp <= { frame_input_data_tmp[OUT_DATA_WIDTH-1:OUT_DATA_WIDTH-2*IN_DATA_WIDTH], bus_convert_rx_data, {(OUT_DATA_WIDTH-3*IN_DATA_WIDTH){1'b0}} } ;
                        frame_input_mod_tmp <=  frame_input_mod_tmp + IN_BYTE_PER_PHASE ;
                    end
                    ({ IN_REC_STAGE2, 1'b1 }): begin
                        frame_input_data_tmp <= { frame_input_data_tmp[OUT_DATA_WIDTH-1:OUT_DATA_WIDTH-2*IN_DATA_WIDTH], bus_convert_rx_data, {(OUT_DATA_WIDTH-3*IN_DATA_WIDTH){1'b0}} } ;
                        if(frame_input_mod_all_zero)
                            frame_input_mod_tmp <=  frame_input_mod_tmp + IN_BYTE_PER_PHASE ;
                        else
                            frame_input_mod_tmp <=  frame_input_mod_tmp + bus_convert_rx_mod ;
                    end

                    ({ IN_REC_STAGE3, 1'b0 }): begin
                        frame_input_data_tmp <= { frame_input_data_tmp[OUT_DATA_WIDTH-1:OUT_DATA_WIDTH-3*IN_DATA_WIDTH], bus_convert_rx_data/*, {(OUT_DATA_WIDTH-4*IN_DATA_WIDTH){1'b0}}*/ } ;
                        frame_input_mod_tmp <=  frame_input_mod_tmp + IN_BYTE_PER_PHASE ;
                    end
                    ({ IN_REC_STAGE3, 1'b1 }): begin
                        frame_input_data_tmp <= { frame_input_data_tmp[OUT_DATA_WIDTH-1:OUT_DATA_WIDTH-3*IN_DATA_WIDTH], bus_convert_rx_data/*, {(OUT_DATA_WIDTH-4*IN_DATA_WIDTH){1'b0}}*/ } ;
                        if(frame_input_mod_all_zero)
                            frame_input_mod_tmp <=  frame_input_mod_tmp + IN_BYTE_PER_PHASE ;
                        else
                            frame_input_mod_tmp <=  frame_input_mod_tmp + bus_convert_rx_mod ;
                    end

                    ({ IN_REC_STAGE4, 1'b0 }): begin
                        frame_input_data_tmp <= { bus_convert_rx_data, {(OUT_DATA_WIDTH-IN_DATA_WIDTH){1'b0}} } ;
                        frame_input_mod_tmp <=  IN_BYTE_PER_PHASE ;
                    end
                    ({ IN_REC_STAGE4, 1'b1 }): begin
                        frame_input_data_tmp <= { bus_convert_rx_data, {(OUT_DATA_WIDTH-IN_DATA_WIDTH){1'b0}} } ;
                        frame_input_mod_tmp <=  bus_convert_rx_mod ;
                    end

                    default: begin // when "IN_IDLE + eop"
                        frame_input_data_tmp <= frame_input_data_tmp ;
                        frame_input_mod_tmp  <= frame_input_mod_tmp ;
                    end
                endcase
            end
            else begin
                frame_input_data_tmp <= frame_input_data_tmp ;
                frame_input_mod_tmp <= frame_input_mod_tmp ;
            end
        end
    end

    //----------------------------------------------
    // sop flip-flop
    //----------------------------------------------
    always@(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            frame_input_sop_ff1 <= 1'b0 ;
            frame_input_sop_ff2 <= 1'b0 ;
            frame_input_sop_ff3 <= 1'b0 ;
            frame_input_sop_ff4 <= 1'b0 ;
        end
        else begin
            frame_input_sop_ff1 <= bus_convert_rx_sop ;
            frame_input_sop_ff2 <= frame_input_sop_ff1 ;
            frame_input_sop_ff3 <= frame_input_sop_ff2 ;
            frame_input_sop_ff4 <= frame_input_sop_ff3 ;
        end
    end

    //----------------------------------------------
    // hand-shaking signal between "input FSM" and "output FSM"
    //----------------------------------------------
    always@(posedge clk or negedge rst_n) begin
        if(!rst_n)
            input_FSM_finish <= 1'b0 ;
        else if(frame_in_state_c == IN_REC_END)
            input_FSM_finish <= 1'b1 ;
        else 
            input_FSM_finish <= 1'b0 ;
    end

    //----------------------------------------------
    // feedback signal
    //----------------------------------------------
    // "input_FSM" has finished a frame transfer(stay "IN_REC_END" state) = 0 
    reg input_FSM_rdy ;
    always@(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            input_FSM_rdy <= 1'b1 ;
        end
        else if(frame_in_state_c == IN_REC_END)
            input_FSM_rdy <= 1'b0 ;
        else 
            input_FSM_rdy <= 1'b1 ;
    end
    assign bus_convert_rx_rdy = input_FSM_rdy ;


//**************************************************
// FIFO input
//**************************************************
    //----------------------------------------------
    // signal declare
    //----------------------------------------------
    reg [1+1+OUT_MOD_WIDTH+OUT_DATA_WIDTH-1:0] fifo_write_data ; //134-bit (sop + eop + mod + data)
    reg fifo_write_en ;
    reg bus_convert_rx_eop_ff1 ;
    reg bus_convert_rx_val_ff1 ;

    //----------------------------------------------
    // signal delay
    //----------------------------------------------
    // "input FSM" delay
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            frame_in_state_c_ff1 <= IN_IDLE ;
        else 
            frame_in_state_c_ff1 <= frame_in_state_c ;
    end
    // "frame input mod" and "frame input data" delay
    always@(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            frame_input_data_tmp_ff1 <= { (OUT_DATA_WIDTH){1'b0} } ;
            frame_input_mod_tmp_ff1 <= { (OUT_MOD_WIDTH){1'b0} } ;
        end
        else begin
            frame_input_data_tmp_ff1 <= frame_input_data_tmp ;
            frame_input_mod_tmp_ff1 <= frame_input_mod_tmp ;
        end
    end
    // "rx eop" delay
    always@(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            bus_convert_rx_eop_ff1 <= 1'b0 ;
        end
        else begin
            bus_convert_rx_eop_ff1 <= bus_convert_rx_eop ;
        end
    end 
    // "rx val" delay
    always@(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            bus_convert_rx_val_ff1 <= 1'b0 ;
        end
        else begin
            bus_convert_rx_val_ff1 <= bus_convert_rx_val ;
        end
    end 
    
    //----------------------------------------------
    // FIFO input port (default 1-delay from "input FSM")
    //----------------------------------------------
    always@(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            fifo_write_data <= {(1+1+OUT_MOD_WIDTH+OUT_DATA_WIDTH){1'b0}} ;
            fifo_write_en <= 1'b0 ;
        end
        else if(bus_convert_rx_val_ff1 == 1'b1) begin
            case( {frame_in_state_c_ff1} )
                IN_REC_STAGE3: begin // normal write
                    fifo_write_data <= {frame_input_sop_ff4, bus_convert_rx_eop_ff1, frame_input_mod_tmp, frame_input_data_tmp} ;
                    fifo_write_en <= 1'b1 ; 
                end

                IN_REC_STAGE1, 
                IN_REC_STAGE2,
                IN_REC_STAGE4: begin
                    fifo_write_data <= {frame_input_sop_ff4, bus_convert_rx_eop_ff1, frame_input_mod_tmp, frame_input_data_tmp} ;
                    fifo_write_en <= bus_convert_rx_eop_ff1 ;
                end

                default: begin
                    fifo_write_data <= {(1+1+OUT_MOD_WIDTH+OUT_DATA_WIDTH){1'b0}} ;
                    fifo_write_en <= 1'b0 ;
                end
            endcase
        end
        else begin
            fifo_write_data <= {(1+1+OUT_MOD_WIDTH+OUT_DATA_WIDTH){1'b0}} ;
            fifo_write_en <= 1'b0 ;
        end
    end

//**************************************************
// FIFO output
//**************************************************
    //----------------------------------------------
    // signal declare
    //----------------------------------------------
    wire [1+1+OUT_MOD_WIDTH+OUT_DATA_WIDTH-1:0] fifo_read_data ; //133-bit
    reg fifo_read_en ;

    //----------------------------------------------
    // read enable
    //----------------------------------------------
    // output frame count declare
    reg [10:0]          out_frame_counter ; // increase 16 per cycle, means: "out_frame_counter" has been read out!
    reg [10:0]          this_frame_length ; // get the "frame length" when jump from "OUT FSM"
    // output FSM declare
    reg [1:0]                   frame_out_state_c ;
    reg [1:0]                   frame_out_state_n ;
    localparam OUT_IDLE         = 2'd0 ;
    localparam OUT_WAIT_DATA    = 2'd1 ;
    localparam OUT_DATA         = 2'd2 ;
    localparam OUT_DATA_END     = 2'd3 ;

    always@(posedge clk or negedge rst_n) begin
        if(!rst_n)
            fifo_read_en <= 1'b0 ;
        else if( ((frame_out_state_c == OUT_IDLE) && (input_FSM_finish == 1'b1) && (unpack_rdy == 1'b1)) || // jump from "output FSM"
                    (frame_out_state_c == OUT_WAIT_DATA) ||
                    ((frame_out_state_c == OUT_DATA) && !((out_frame_counter + 11'd16) >= this_frame_length)) ) // will jump to "output data end"
            fifo_read_en <= 1'b1 ;
        else
            fifo_read_en <= 1'b0 ;
    end

//**************************************************
// FIFO instance
//**************************************************
`ifdef FPGA_MODE
    rx_bus_convert_fifo_256d_134w inst_rx_bus_convert_fifo_256d_134w(
        .clk            (clk),                  // input wire clk
        .rst           (!rst_n),               // input wire rst

        .din            (fifo_write_data),      // input wire [133 : 0] din
        .wr_en          (fifo_write_en),        // input wire wr_en
        .rd_en          (fifo_read_en),         // input wire rd_en
        .dout           (fifo_read_data),       // output wire [133 : 0] dout
        .full           (),                     // output wire full
        .empty          ()                      // output wire empty
    ) ;
`else
    fifo_256d_134w_wrapper inst_rx_bus_convert_fifo_256d_134w(
        .clk            (clk),                  // input wire clk
        .rst           (!rst_n),               // input wire rst
        .ram_2p_cfg_register(ram_2p_cfg_register),

        .din            (fifo_write_data),      // input wire [133 : 0] din
        .wr_en          (fifo_write_en),        // input wire wr_en
        .rd_en          (fifo_read_en),         // input wire rd_en
        .dout           (fifo_read_data),       // output wire [133 : 0] dout
        .full           (),                     // output wire full
        .empty          ()                      // output wire empty
    ) ;
`endif

//**************************************************
// FIFO output FSM
//**************************************************
    //----------------------------------------------
    // signal declare (above)
    //----------------------------------------------
    //----------------------------------------------
    // FSM declare (above)
    //----------------------------------------------
    

    // output FSM (2.1)
    always@(posedge clk or negedge rst_n) begin
        if(!rst_n) 
            frame_out_state_c <= OUT_IDLE ;
        else
            frame_out_state_c <= frame_out_state_n ;
    end
    // output FSM (2.2)
    always@(*) begin
        case(frame_out_state_c)
            OUT_IDLE: begin
                if( (input_FSM_finish == 1'b1) && (unpack_rdy == 1'b1) )
                    frame_out_state_n = OUT_WAIT_DATA ;
                else
                    frame_out_state_n = OUT_IDLE ;
            end
            OUT_WAIT_DATA: begin
                frame_out_state_n = OUT_DATA ;
            end
            OUT_DATA: begin
                if( out_frame_counter >= this_frame_length ) begin
                    frame_out_state_n = OUT_DATA_END ;
                end
                else
                    frame_out_state_n = OUT_DATA ;
            end
            OUT_DATA_END: begin
                frame_out_state_n = OUT_IDLE ;
            end

            default: begin
                frame_out_state_n = OUT_IDLE ;
            end
        endcase
    end

    // shaking-hand beteween "input FSM" and "output FSM"
    always@(posedge clk or negedge rst_n) begin
        if(!rst_n)
            output_FSM_rdy <= 1'b0 ;
        else if( (frame_out_state_c == OUT_IDLE )&& (unpack_rdy == 1'b1)) 
            output_FSM_rdy <= 1'b1 ;
        else 
            output_FSM_rdy <= 1'b0 ;
    end

    // get this frame's length
    always@(posedge clk or negedge rst_n) begin
        if(!rst_n)
            this_frame_length <= 11'b0 ;
        else
            if( (frame_out_state_c == OUT_IDLE) && (input_FSM_finish == 1'b1) && (unpack_rdy == 1'b1) )
                this_frame_length <= rx_frame_length ;
            else
                this_frame_length <= this_frame_length ;
    end
    // increase the "out frame" counter 
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            out_frame_counter <= 11'b0 ;
        else if( (frame_out_state_c == OUT_WAIT_DATA) || (frame_out_state_c == OUT_DATA) )
            out_frame_counter <= (out_frame_counter + 11'd16) ;
        else 
            out_frame_counter <= 11'b0 ;
    end

//**************************************************
// output FSM
//**************************************************
    //----------------------------------------------
    // signal declare
    //----------------------------------------------
    reg [OUT_DATA_WIDTH-1:0]    unpack_data_r ;
    reg [OUT_MOD_WIDTH-1:0]     unpack_mod_r ;
    reg                         unpack_sav_r ;
    reg                         unpack_val_r ;
    reg                         unpack_sop_r ;
    reg                         unpack_eop_r ;

    // output "sav"
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            unpack_sav_r <= 1'b0 ;
        else if( frame_out_state_n != OUT_IDLE )
            unpack_sav_r <= 1'b1 ;
        else
            unpack_sav_r <= 1'b0 ;
    end
    // output "data" "mod" "val" "sop" "eop"
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            unpack_data_r <= {(OUT_DATA_WIDTH){1'b0}} ;
            unpack_mod_r <= {(OUT_MOD_WIDTH){1'b0}} ;
            unpack_val_r <= 1'b0 ;
            unpack_sop_r <= 1'b0 ;
            unpack_eop_r <= 1'b0 ;
        end
        else if( frame_out_state_c == OUT_DATA ) begin
            unpack_data_r <= fifo_read_data [OUT_DATA_WIDTH-1                       :   0                               ] ;
            unpack_mod_r <= fifo_read_data  [OUT_MOD_WIDTH+OUT_DATA_WIDTH-1         :   OUT_DATA_WIDTH                  ] ;
            unpack_sop_r <= fifo_read_data  [OUT_MOD_WIDTH+OUT_DATA_WIDTH+1         :   OUT_MOD_WIDTH+OUT_DATA_WIDTH+1  ] ;
            unpack_eop_r <= fifo_read_data  [OUT_MOD_WIDTH+OUT_DATA_WIDTH           :   OUT_MOD_WIDTH+OUT_DATA_WIDTH    ] ;
            unpack_val_r <= 1'b1 ;
        end
        else begin
            unpack_data_r <= {(OUT_DATA_WIDTH){1'b0}} ;
            unpack_mod_r <= {(OUT_MOD_WIDTH){1'b0}} ;
            unpack_val_r <= 1'b0 ;
            unpack_sop_r <= 1'b0 ;
            unpack_eop_r <= 1'b0 ;
        end
    end

    // signal assign
    assign unpack_data = unpack_data_r ;
    assign unpack_mod = unpack_mod_r ;
    assign unpack_sav = unpack_sav_r ;
    assign unpack_val = unpack_val_r ;
    assign unpack_sop = unpack_sop_r ;
    assign unpack_eop = unpack_eop_r ;

endmodule
